using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls.WebParts;
using System.Xml.XPath;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
using PS.Practices.Provisioner;
using PS.Practices.Util;
using WebPart = System.Web.UI.WebControls.WebParts.WebPart;
#if MOSS
using Microsoft.SharePoint.Publishing;
#endif

namespace PS.Practices.SiteBuilder.Features.Schema
{
    static class WebParser
    {
        internal static void ProcessWebNode(SPWeb web, XPathNavigator xWeb)
        {
            string title = xWeb.GetAttribute("Title", string.Empty);
            string url = xWeb.GetAttribute("Url", string.Empty);
            string template = xWeb.GetAttribute("Template", string.Empty);
            string description = xWeb.GetAttribute("Description", string.Empty);
            string strLcid = xWeb.GetAttribute("LCID", string.Empty);

            if (string.IsNullOrEmpty(title))
            {
                Logger.Info("Required attribute Title is missing: " + xWeb.OuterXml);
                return;
            }

            if (string.IsNullOrEmpty(template))
            {
                Logger.Info("Required attribute Template is missing: " + xWeb.OuterXml);
                return;
            }

            if (string.IsNullOrEmpty(url))
            {
                Regex cleaner = new Regex(@"[\W]");
                url = cleaner.Replace(title, "").ToLower(CultureInfo.InvariantCulture);
            }

            uint lcid = web.Language;
            if (!string.IsNullOrEmpty(strLcid))
            {
                uint.TryParse(strLcid, out lcid);
            }

            bool uniquePerm = ParserUtil.GetAttributeBool(xWeb, "UniquePermissions", false);

            using (SPWeb newWeb = web.Webs.Add(url, title, description, lcid, template, uniquePerm, false))
            {
                bool inheritTopNav = ParserUtil.GetAttributeBool(xWeb, "InheritTopNav", true);

                if (inheritTopNav)
                {
                    newWeb.Navigation.UseShared = true;
                    newWeb.Update();
                }

                if (xWeb.SelectChildren(XPathNodeType.Element).Count > 0)
                {
                    Interpreter parser = new Interpreter();
                    parser.Process(newWeb, xWeb);
                }
            }
        }

        internal static void ApplyWebTemplate(SPWeb web, XPathNavigator doc)
        {
            string template = ParserUtil.GetAttribute(doc, "ApplyWebTemplate");
            if (!string.IsNullOrEmpty(template))
            {
                web.ApplyWebTemplate(template);
            }
        }

        internal static void RestrictSiteTemplates(SPWeb web, XPathNavigator doc)
        {
            XPathNavigator templates = doc.SelectSingleNode("RestrictSiteTemplates");
            if (templates == null)
            {
                return;
            }

            Dictionary<string, uint> coll = new Dictionary<string, uint>();
            XPathNodeIterator nodes = templates.Select("Template");
            foreach (XPathNavigator node in nodes)
            {
                string attribute = ParserUtil.GetAttribute(node, "LCID");
                uint lcid = web.Language;
                if (!string.IsNullOrEmpty(attribute))
                {
                    uint.TryParse(attribute, out lcid);
                }
                coll.Add(node.Value, lcid);
            }

            WebUtil.RestrictSiteTemplates(web, coll);
            web.Update();
        }

        internal static void RestoreWebProperties(SPWeb web, XPathNavigator doc)
        {
            XPathNavigator xWebProperties = doc.SelectSingleNode("WebProperties");
            if (xWebProperties == null)
            {
                return;
            }

            foreach (XPathNavigator xWebProperty in xWebProperties.SelectChildren(XPathNodeType.Element))
            {
                string strKey = ParserUtil.GetAttribute(xWebProperty, "Key");
                string strValue = ParserUtil.GetAttribute(xWebProperty, "Value");
                if (!string.IsNullOrEmpty(strKey) &&
                    !string.IsNullOrEmpty(strValue))
                {
                    string key = strKey;
                    string value = strValue;
                    if (web.Properties.ContainsKey(key))
                    {
                        web.Properties[key] = value;
                    }
                    else
                    {
                        web.Properties.Add(key, value);
                    }
                }
            }

            web.Properties.Update();
        }

        internal static void RestoreCss(SPWeb web, XPathNavigator doc)
        {
            XPathNavigator xCustomizedCssFiles = doc.SelectSingleNode("CustomizedCssFiles");
            if (xCustomizedCssFiles == null)
            {
                return;
            }

            List<string> customizedCssFiles = new List<string>();
            foreach (XPathNavigator xCustomizedCssFile in xCustomizedCssFiles.SelectChildren(XPathNodeType.Element))
            {
                string xName = ParserUtil.GetAttribute(xCustomizedCssFile, "Name");
                if (string.IsNullOrEmpty(xName))
                {
                    continue;
                }

                customizedCssFiles.Add(xName);
            }

            SPFolder rootFolder = web.RootFolder;
            SPFolder cssFolder = null;
            string targetFolder = "_styles";
            foreach (SPFolder subFolder in rootFolder.SubFolders)
            {
                if (subFolder.Name.ToLower(CultureInfo.InvariantCulture) == targetFolder)
                {
                    cssFolder = subFolder;
                    break;
                }
            }

            if (cssFolder == null)
            {
                return;
            }

            foreach (SPFile file in cssFolder.Files)
            {
                if (customizedCssFiles.Contains(file.Name))
                {
                    web.CustomizeCss(file.Name);
                    web.Update();
                }
            }
        }

        internal static void FixupWebACL(SPWeb web, XPathNavigator doc)
        {
            XPathNavigator xFixupAcl = doc.SelectSingleNode("FixupWebACL");

            Logger.Info("Configuring web ACL");

            if (xFixupAcl != null)
            {
                bool inheritTopNav = ParserUtil.GetAttributeBool(xFixupAcl, "InheritTopNav", web.Navigation.UseShared);
                if (inheritTopNav != web.Navigation.UseShared)
                {
                    web.Navigation.UseShared = inheritTopNav;
                    web.Update();
                }

                bool respectInheritance = ParserUtil.GetAttributeBool(xFixupAcl, "RespectInheritanceSetting", true);

                if (respectInheritance && !(web.HasUniqueRoleDefinitions || web.HasUniqueRoleAssignments))
                {
                    Logger.Info("RespectInheritanceSetting setting is set or assumed as TRUE and the web does not seem to have unique permissions specified. the FixupWebACL actions shall be skipped.");
                    return;
                }

                string mode = ParserUtil.GetAttribute(xFixupAcl, "RoleAssignments");
                if (!string.IsNullOrEmpty(mode))
                {
                    if (mode.StartsWith("Break"))
                    {
                        Logger.Info("Breaking role inheritance: " + mode);
                        try
                        {
                            web.BreakRoleInheritance(mode == "BreakAndCopy");
                        }
                        catch (Exception e)
                        {
                            Logger.Info("Error while braking web role inheritance: " + mode);
                            Logger.Error(e);
                        }
                    }
                }

                foreach (XPathNavigator node in xFixupAcl.SelectChildren(XPathNodeType.Element))
                {
                    switch (node.LocalName)
                    {
                        case "SiteGroup":
                            string action = ParserUtil.GetAttribute(node, "Action");
                            string name = ParserUtil.GetAttribute(node, "Name");
                           // name = name.Replace(",", " ");
                            switch (action)
                            {
                                case "Create":
                                    Logger.Info("Creating cross site group: " + name);
                                    AclUtil.CreateWebGroup(name, ParserUtil.GetAttribute(node, "Description"), web);
                                    break;
                                case "Delete":
                                    Logger.Info("Deleting cross site group: " + name);
                                    AclUtil.DeleteWebGroup(name, web);
                                    break;
                                default:
                                    Logger.Info("Unrecognized action: " + action);
                                    break;
                            }

                            break;

                        case "User":
                            action = ParserUtil.GetAttribute(node, "Action");
                            string loginName = ParserUtil.GetAttribute(node, "LoginName");
                            string displayName = ParserUtil.GetAttribute(node, "DisplayName");
                            if (string.IsNullOrEmpty(displayName))
                                displayName = loginName;

                            switch (action)
                            {
                                case "Add":
                                    Logger.Info("Adding user record: " + loginName);
                                    SPUser user = AclUtil.ResolveUser(loginName, web);
                                    string email = ParserUtil.GetAttribute(node, "Email");
                                    string description = ParserUtil.GetAttribute(node, "Description");
                                    if (string.IsNullOrEmpty(description))
                                    {
                                        description = string.Empty;
                                    }
                                    if (user == null)
                                    {
                                        web.AllUsers.Add(loginName, email, displayName, description);
                                    }
                                    //else
                                    //{
                                    //    if (displayName != loginName)
                                    //        user.Name = displayName;
                                    //    if (email != null)
                                    //        user.Email = email;
                                    //    if (description != null)
                                    //        user.Notes = description;
                                    //    user.Update();
                                    //}
                                    break;
                                case "Remove":
                                    Logger.Info("Removing user from site: " + loginName);
                                    try
                                    {
                                        web.AllUsers.Remove(loginName);
                                    }
                                    catch (SPException)
                                    {
                                        Logger.Info("Could not remove ");
                                    }
                                    break;
                                default:
                                    Logger.Info("Unrecognized action: " + action);
                                    break;
                            }

                            break;

                        case "AssociateSiteGroup":
                            string role = ParserUtil.GetAttribute(node, "Role");
                            name = ParserUtil.GetAttribute(node, "Name");

                            name = ParserUtil.ReplaceLiveFields(name, web);
                            //name = name.Replace(",", " ");
                            SPGroup group = AclUtil.ResolveSiteGroup(name, web);
                            if (group == null)
                            {
                                continue;
                            }

                            switch (role)
                            {
                                case "Owner":
                                    Logger.Info("Associating group " + group.Name + " for role " + role);
                                    web.AssociatedOwnerGroup = group;
                                    role = "Full Control";
                                    break;

                                case "Member":
                                    Logger.Info("Associating group " + group.Name + " for role " + role);
                                    web.AssociatedMemberGroup = group;
                                    role = "Contribute";
                                    break;

                                case "Visitor":
                                    Logger.Info("Associating group " + group.Name + " for role " + role);
                                    web.AssociatedVisitorGroup = group;
                                    role = "Read";
                                    break;

                            }

                            SPRoleDefinition roleDef;
                            try
                            {
                                roleDef = web.RoleDefinitions[role];
                            }
                            catch (SPException)
                            {
                                Logger.Info("Unrecognized role: " + role);
                                continue;
                            }

                            SPRoleAssignment assignment = new SPRoleAssignment(group);
                            assignment.RoleDefinitionBindings.Add(roleDef);
                            web.RoleAssignments.Add(assignment);


                            break;

                        case "PrincipalRole":
                            role = ParserUtil.GetAttribute(node, "Role");
                            name = ParserUtil.GetAttribute(node, "Name");
                            string type = ParserUtil.GetAttribute(node, "Type");
                            string associateAction = ParserUtil.GetAttribute(node, "Action");

                            name = ParserUtil.ReplaceLiveFields(name, web);

                            SPPrincipal spPrincipal = type == "SiteGroup" ? AclUtil.ResolveSiteGroup(name, web)
                                                          : (SPPrincipal)AclUtil.ResolveUser(name, web);
                            if (spPrincipal == null)
                            {
                                continue;
                            }

                            SPRoleDefinition definition;
                            try
                            {
                                definition = web.RoleDefinitions[role];
                            }
                            catch (SPException)
                            {
                                Logger.Info("Unrecognized role: " + role);
                                continue;
                            }

                            try
                            {
                                if (associateAction == "Add")
                                {
                                    SPRoleAssignment assignment2 = new SPRoleAssignment(spPrincipal);
                                    assignment2.RoleDefinitionBindings.Add(definition);
                                    web.RoleAssignments.Add(assignment2);
                                }
                                else
                                {
                                    SPRoleAssignment roleAssignment = web.RoleAssignments.GetAssignmentByPrincipal(spPrincipal);
                                    if (roleAssignment != null)
                                    {
                                        roleAssignment.RoleDefinitionBindings.Remove(definition);
                                        roleAssignment.Update();
                                    }
                                }
                            }
                            catch (SPException e)
                            {
                                Logger.Error(e);
                            }
                            break;


                        default:
                            Logger.Info("Unsupported node: " + node.OuterXml);
                            break;
                    }


                }

                string syncQuickLaunch = ParserUtil.GetAttribute(xFixupAcl, "SyncGroupQuickLaunch");
                if (!string.IsNullOrEmpty(syncQuickLaunch))
                {
                    Logger.Info("Syncing group quick launch");
                    AclUtil.SyncGroupQuickLaunch(web, syncQuickLaunch == "FromAssociations");
                }

                web.Update();
            }
        }

        internal static void FixupFiles(SPWeb web, XPathNavigator doc)
        {
            XPathNodeIterator xFixupFiles = doc.Select("FixupFiles/File");
            foreach (XPathNavigator xFixupFile in xFixupFiles)
            {
                string path = ParserUtil.GetAttribute(xFixupFile, "Path");
                if (string.IsNullOrEmpty(path))
                {
                    continue;
                }

                SPFile file = web.GetFile(path);
                if (file == null)
                {
                    Logger.Info("Could not locate file by its path:" + path);
                    continue;
                }

                SPListItem item = null;

                try
                {
                    item = file.Item;
                }
                catch (SPException)
                {
                }


                SPLimitedWebPartManager manager;
#if MOSS
                PublishingPage page = null;
                if (item != null) 
                {
                    if (PublishingPage.IsPublishingPage(item))
                    {
                        page = PublishingPage.GetPublishingPage(item);
                        if (page.ListItem.File.CheckOutStatus == SPFile.SPCheckOutStatus.None)
                        {
                            page.CheckOut();
                            page.Update();
                        }
                    } else
                    {
                        try
                        {
                            file.CheckOut();
                            file.Update();
                        }catch(SPException)
                        {
                        }
                    }
                }
#else
                try
                {
                    file.CheckOut();
                    file.Update();
                }
                catch (SPException)
                {
                }
#endif

                using (manager = file.GetLimitedWebPartManager(PersonalizationScope.Shared))
                {
                    SPLimitedWebPartCollection pageWebParts = manager.WebParts;
                    if (pageWebParts == null)
                    {
#if MOSS
                        if (page != null)
                            page.CheckIn("");
#endif
                        continue;
                    }

                    foreach (XPathNavigator node in xFixupFile.SelectChildren(XPathNodeType.Element))
                    {
                        switch (node.LocalName)
                        {
                            case "WebPart":

                                string wpTitle = ParserUtil.GetAttribute(node, "Title");

                                WebPart part = null;

                                foreach (WebPart p in pageWebParts)
                                {
                                    if (string.Compare(p.Title, wpTitle, true) == 0)
                                    {
                                        part = p;
                                        break;
                                    }
                                }

                                if (part == null)
                                    continue;

                                bool saveChanges = ProcessWebPart(part, node.SelectChildren(XPathNodeType.Element), web, manager);

                                if (saveChanges)
                                    try
                                    {
                                        manager.SaveChanges(part);
                                    }
                                    catch (WebPartPageUserException)
                                    {
                                    }
                                break;
                            case "DataViewsInZone":
                                RestoreDataViewInZone(doc, manager, pageWebParts, web);
                                break;
                            case "DataViewsOutZone":
                                RestoreDataViewOutZone(web, file, doc);
                                break;
                        }
                    }
                }

                //Dispose/Close was not called on SPLimitedWebPartManager.Web
                manager.Web.Dispose();

                const string txt = "Post provisioning modifications";
#if MOSS
                if (page != null)
                {
                    string comment = txt;
                    page.CheckIn(comment);
                    file.Publish(comment);

                    try
                    {
                        file.Approve(comment);
                    }
                    catch(SPException)
                    {
                    }
                }
                else 
#endif
                if (item != null)
                {
                    try
                    {
                        file.CheckIn(txt);
                        file.Publish(txt);
                        file.Approve(txt);
                    }
                    catch (SPException)
                    {
                    }
                }

            }
        }

        internal static bool ProcessWebPart(WebPart webPart, XPathNodeIterator nodes, SPWeb web, SPLimitedWebPartManager manager)
        {
            bool saveChanges = true;
            foreach (XPathNavigator node in nodes)
            {
                switch (node.LocalName)
                {
                    case "SetListViewAndToolbar":

                        ListViewWebPart lvwp = webPart as ListViewWebPart;
                        if (lvwp == null)
                        {
                            Logger.Info("WebPart is not of ListViewWebPart type: " + webPart.Title);
                            continue;
                        }

                        SPList list = null;
                        string listUrl = ParserUtil.GetAttribute(node, "ListUrl");
                        if (!string.IsNullOrEmpty(listUrl))
                        {
                            try
                            {
                                list = web.GetList(listUrl);
                            }
                            catch (SPException)
                            {
                            }
                        }

                        if (list == null)
                        {
                            string listName = ParserUtil.GetAttribute(node, "List");

                            if (string.IsNullOrEmpty(listName))
                            {
                                try
                                {
                                    Guid listGuid = new Guid(lvwp.ListName);
                                    list = web.Lists[listGuid];
                                }
                                catch (FormatException)
                                {
                                }
                                catch (SPException)
                                {
                                }
                            }
                            else
                            {
                                try
                                {
                                    list = web.Lists[listName];
                                }
                                catch (SPException)
                                {
                                }
                            }
                        }

                        if (list == null)
                        {
                            Logger.Info("Could not resolve list from web: " + node.OuterXml);
                            continue;
                        }
                        SPView view;
                        string viewName = ParserUtil.GetAttribute(node, "View");
                        if (!string.IsNullOrEmpty(viewName))
                        {
                            view = list.Views[viewName];

                        }
                        else
                        {
                            view = list.Views[new Guid(lvwp.ViewGuid)];
                        }

                        uint toolbar = 0;

                        string strToolbar = ParserUtil.GetAttribute(node, "Toolbar");
                        if (!string.IsNullOrEmpty(strToolbar))
                        {
                            if (!uint.TryParse(strToolbar, out toolbar))
                            {
                                switch (strToolbar)
                                {
                                    case "Standard":
                                        toolbar = 0;
                                        break;
                                    case "Freeform":
                                        toolbar = 1;
                                        break;
                                    default:
                                        toolbar = 2;
                                        break;
                                }
                            }
                        }

                        //ListViewWebPart w2 = new  ListViewWebPart();
                        //string zoneId = lvwp.ZoneID;
                        ////w2.ZoneID = zoneId;
                        //w2.ListName = list.ID.ToString("B").ToUpper();
                        //w2.ViewGuid = view.ID.ToString("B").ToUpper();
                        //w2.Title = lvwp.Title;
                        //int index = lvwp.ZoneIndex;

                        //manager.DeleteWebPart(lvwp);
                        //manager.AddWebPart(w2, zoneId, index);


                        //saveChanges = false;
                        WebPartUtil.SetListViewWebPartView(lvwp, view, toolbar, manager);

                        break;

                    case "UpdateTitle":

                        Dictionary<string, string> dic = null;
                        string set;
                        ListParser.ReadReplaceInfo(dic, node, out set);

                        ParserUtil.UpdateWPTitle(webPart, web, set, dic);
                        break;

                    case "SetProperty":
                        string propName = ParserUtil.GetAttribute(node, "Name");
                        if (string.IsNullOrEmpty(propName))
                        {
                            continue;
                        }

                        string propValue = ParserUtil.GetAttribute(node, "Value");
                        if (string.IsNullOrEmpty(propValue))
                        {
                            continue;
                        }

                        WebPartUtil.SetWPProperty(webPart, propName, propValue, web);

                        break;
                }
            }
            return saveChanges;
        }

        internal static void ProvisionWebs(SPWeb web, XPathNavigator root)
        {
            XPathNavigator xWebsNode = root.SelectSingleNode("ProvisionWebs");
            if (xWebsNode == null)
                return;

            foreach (XPathNavigator node in xWebsNode.Select("Web"))
            {
                ProcessWebNode(web, node);
            }
        }

        internal static void SubstituteGuidInZone(SPWeb web, SPLimitedWebPartManager manager, DataFormWebPart dataForm, XPathNavigator doc)
        {
            XPathNavigator xListInstances = doc.SelectSingleNode("ListInstances");
            if (xListInstances == null)
            {
                return;
            }
            foreach (XPathNavigator xListInstance in xListInstances.SelectChildren(XPathNodeType.Element))
            {
                string oldId = ParserUtil.GetAttribute(xListInstance, "Id");
                string title = ParserUtil.GetAttribute(xListInstance, "Title");

                if (string.IsNullOrEmpty(oldId) ||
                    string.IsNullOrEmpty(title))
                {
                    //BUG: Maybe continue; ?
                    return;
                }

                SPList list;
                try
                {
                    list = web.Lists[title];
                }
                catch (ArgumentException)
                {
                    continue;
                }

                if (list == null)
                {
                    continue;
                }
                string newId = list.ID.ToString();

                dataForm.ParameterBindings = Regex.Replace(dataForm.ParameterBindings, oldId, newId, RegexOptions.IgnoreCase);
                dataForm.DataSourcesString = Regex.Replace(dataForm.DataSourcesString, oldId, newId, RegexOptions.IgnoreCase);
                dataForm.Xsl = Regex.Replace(dataForm.Xsl, oldId, newId, RegexOptions.IgnoreCase);

                manager.SaveChanges(dataForm);
            }
        }

        internal static void RestoreDataViewOutZone(SPWeb web, SPFile file, XPathNavigator doc)
        {
            string fileName = file.Name;

            string content;
            using (StreamReader reader = new StreamReader(file.OpenBinaryStream()))
            {
                content = reader.ReadToEnd();
                content = SubstituteGuid(web, doc, content);
            }

            UTF8Encoding encoder = new UTF8Encoding();
            byte[] contentAsBytes = encoder.GetBytes(content);

            // store to temporary file
            SPFile temp = web.Files.Add("temp.aspx", contentAsBytes);

            SPLimitedWebPartManager sourceManager = file.GetLimitedWebPartManager(PersonalizationScope.Shared);
            SPLimitedWebPartCollection sourceWebParts = sourceManager.WebParts;

            SPLimitedWebPartManager targetManager = temp.GetLimitedWebPartManager(PersonalizationScope.Shared);
            foreach (WebPart webPart in sourceWebParts)
            {
                string zoneId = sourceManager.GetZoneID(webPart);
                targetManager.AddWebPart(webPart, zoneId, webPart.ZoneIndex);
            }

            foreach (SPWebPartConnection connection in sourceManager.SPWebPartConnections)
            {
                targetManager.SPConnectWebParts(connection.Provider, connection.ProviderConnectionPoint, connection.Consumer, connection.ConsumerConnectionPoint, connection.Transformer);
            }

            //Dispose/Close was not called on SPLimitedWebPartManager.Web
            sourceManager.Web.Dispose();

            file.Delete();

            temp.CopyTo(fileName);
            temp.Delete();

            web.Update();
        }

        internal static string SubstituteGuid(SPWeb web, XPathNavigator doc, string content)
        {
            string correctContent = content;

            XPathNavigator xListInstances = doc.SelectSingleNode("ListInstances");
            if (xListInstances == null)
            {
                return String.Empty;
            }

            foreach (XPathNavigator xListInstance in xListInstances.SelectChildren(XPathNodeType.Element))
            {

                string oldId = ParserUtil.GetAttribute(xListInstance, "Id");
                string title = ParserUtil.GetAttribute(xListInstance, "Title");

                if (string.IsNullOrEmpty(oldId) ||
                    string.IsNullOrEmpty(title))
                {
                    continue;
                }

                SPList list;
                try
                {
                    list = web.Lists[title];
                }
                catch (ArgumentException)
                {
                    continue;
                }

                if (list == null)
                {
                    return String.Empty;
                }
                string newId = list.ID.ToString();

                correctContent = Regex.Replace(correctContent, oldId, newId, RegexOptions.IgnoreCase);
            }

            return correctContent;
        }

        internal static void RestoreDataViewInZone(XPathNavigator doc, SPLimitedWebPartManager manager, SPLimitedWebPartCollection pageWebParts, SPWeb web)
        {
            foreach (Microsoft.SharePoint.WebPartPages.WebPart webPart in pageWebParts)
            {
                DataFormWebPart dataForm = webPart as DataFormWebPart;
                if (dataForm == null)
                {
                    continue;
                }

                SubstituteGuidInZone(web, manager, dataForm, doc);
            }
        }
    }
}
